Namespace.Use("Application.Sensor.Interface.Object", function(ns) {
    //
    //  Imports.
    //
    var DataSetConfiguration = Namespace.Import("Application.Sensor.DataSet.DataSetConfiguration");
    var LinearTimeRectifier = Namespace.Import("Application.Sensor.Rectifier.LinearTimeRectifier");
    var Inherit = Namespace.Import("Base.Inherit");
    var Utility = Namespace.Import("Base.Utility");
    var UI = Namespace.Import("UI");

    //
    //  Constants.
    //
    var cst_InitialX = 0, cst_InitialY = 0;
    var cst_InitialWidth = 640, cst_InitialHeight = 480;
    var cst_InitialLayer = 0;
    var cst_InitialVisibility = true;

    //
    //  Classes.
    //
    var WaveformChart = Polymorphic.Create(
        [String, Number, Array, Number],
        function(title, length, datasets, interval) {
            //  Let parent class initialize.
            UI.Object.BaseObject.call(this);

            //  Check the datasets.
            if (datasets.length == 0) {
                throw "No dataset.";
            }
            if (!Utility.CheckArrayItemType(datasets, DataSetConfiguration)) {
                throw "Invalid item type of the datasets.";
            }

            //  Check the interval.
            if (interval <= 0) {
                throw "Invalid interval.";
            }

            //  Check the length.
            if (length <= 0) {
                throw "Invalid time length.";
            }

            //  Calculate the range.
            var range_min = null, range_max = null;
            datasets.forEach(function(dataset) {
                var range = dataset.GetRange();
                if (range_min === null) {
                    range_min = range[0];
                    range_max = range[1];
                } else {
                    if (range[0] < range_min) {
                        range_min = range[0];
                    }
                    if (range[1] > range_max) {
                        range_max = range[1];
                    }
                }
            });

            //  Check the range.
            if (range_min >= range_max) {
                throw "Invalid data range.";
            }

            //  Create the figure of the chart.
            var figure = {
                "layout": {
                    "xaxis": {
                        "range": [-length, 0],
                        "zeroline": false
                    },
                    "yaxis": {
                        "rangemode": "nonnegative",
                        "autorange": true
                    },
                    "autosize": false,
                    "width": cst_InitialWidth,
                    "height": cst_InitialHeight,
                    "margin": {
                        "l": 32,
                        "r": 32,
                        "b": 32,
                        "t": 32,
                        "pad": 4
                    }
                },
                "data": [
                ]
            };
            if (title !== null && title.length > 0) {
                figure["layout"]["title"] = title;
            }
            datasets.forEach(function(dataset) {
                figure["data"].push({
                    "x": [],
                    "y": [],
                    "mode": "lines",
                    "line": {
                        "color": dataset.GetColor(),
                        "width": 1
                    },
                    "type": "scatter",
                    "name": dataset.GetName()
                });
            });

            //
            //  Members.
            //
            var last_x = -1, last_y = -1;
            var x = cst_InitialX, y = cst_InitialY;
            var last_width = -1, last_height = -1;
            var width = cst_InitialWidth, height = cst_InitialHeight;
            var last_visibility = !cst_InitialVisibility;
            var visibility = cst_InitialVisibility;
            var last_layer = -1;
            var layer = cst_InitialLayer;
            var rectifier = [], data_buffer = [];
            var element = $("<div></div>").css({
                "position": "absolute",
            }).addClass("plotly-graph-div");

            //  Initialize the rectifiers and the data buffers.
            for (var idx = 0; idx < datasets.length; ++idx) {
                rectifier.push(new LinearTimeRectifier());
                data_buffer.push([]);
            }

            //
            //  Private methods.
            //
            function RecalculateData() {
                for (var idx = 0; idx < datasets.length; ++idx) {
                    //  Fetch from the rectifier.
                    var fetched = rectifier[idx].Get();
                    var rcv_ts = fetched[0], rcv_value = fetched[1];

                    //  Put the received data to the buffer.
                    for (var rcv_idx = 0; rcv_idx < rcv_ts.length; ++rcv_idx) {
                        data_buffer[idx].push([rcv_ts[rcv_idx], rcv_value[rcv_idx]]);
                    }

                    if (data_buffer[idx].length != 0) {
                        //  Get the time-stamp of the oldest sample in the buffer.
                        var oldest = data_buffer[idx][data_buffer[idx].length - 1][0];

                        //  Get the index of the first valid sample.
                        var valid = data_buffer[idx].length - 1;
                        while(valid >= 0 && data_buffer[idx][valid][0] >= oldest - length) {
                            valid--;
                        }
                        if (valid < 0) {
                            valid = 0;
                        }

                        //  Cut off useless samples in the buffer.
                        data_buffer[idx] = data_buffer[idx].splice(valid);

                        //  Build the chart data.
                        var data_x = [];
                        var data_y = [];

                        data_buffer[idx].forEach(function(data_item) {

                            data_x.push(data_item[0] - oldest);
                            data_y.push(data_item[1]["Value"]);
                        });


                        //  Write the data.
                        figure["data"][idx]["x"] = data_x;
                        figure["data"][idx]["y"] = data_y;
                    }
                }
            }


            function RedrawChart() {
                RecalculateData();
                Plotly.redraw(element[0]);
            }

            function RelayoutChart() {
                Plotly.relayout(element[0], figure["layout"]);
            }

            //
            //  Public methods.
            //
            this.GetHTMLElement = function() {
                return element[0];
            };
            this.Invalidate = function() {
                var redraw = false;
                var relayout = false;

                //  Update the position.
                if (last_x != x) {
                    element.css({
                        "left": x
                    });
                    last_x = x;
                    redraw = true;
                }
                if (last_y != y) {
                    element.css({
                        "top": y
                    });
                    last_y = y;
                    redraw = true;
                }

                //  Update the size.
                if (last_width != width) {
                    element.css({
                        "width": width
                    });
                    figure["layout"]["width"] = width;
                    last_width = width;
                    redraw = true;
                    relayout = true;
                }
                if (last_height != height) {
                    element.css({
                        "height": height
                    });
                    figure["layout"]["height"] = height;
                    last_height = height;
                    redraw = true;
                    relayout = true;
                }

                //  Update the visibility.
                if (last_visibility != visibility) {
                    if (visibility == true) {
                        element.show();
                    } else {
                        element.hide();
                    }
                    last_visibility = visibility;
                    redraw = true;
                }

                //  Update the layer.
                if (last_layer != layer) {
                    element.css({
                        "z-index": layer
                    });
                    last_layer = layer;
                    redraw = true;
                }

                //  Relayout.
                if (relayout == true) {
                    RelayoutChart();
                }

                //  Redraw.
                if (redraw == true) {
                    RedrawChart();
                }
            };
            this.AccessPosition = Polymorphic.Create(
                [],
                function() {
                    return [x, y];
                },
                [Number, Number],
                function(new_x, new_y) {
                    last_x = x;
                    last_y = y;
                    x = new_x;
                    y = new_y;
                    this.Invalidate();
                }
            );
            this.AccessSize = Polymorphic.Create(
                [],
                function() {
                    return [width, height];
                },
                [Number, Number],
                function(new_width, new_height) {
                    last_width = width;
                    last_height = height;
                    width = new_width;
                    height = new_height;
                    this.Invalidate();
                }
            );
            this.AccessVisibility = Polymorphic.Create(
                [],
                function() {
                    return visibility;
                },
                [Boolean],
                function(new_visibility) {
                    last_visibility = visibility;
                    visibility = new_visibility;
                    this.Invalidate();
                }
            );
            this.AccessLayer = Polymorphic.Create(
                [],
                function() {
                    return layer;
                },
                [Number],
                function(new_layer) {
                    last_layer = layer;
                    layer = new_layer;
                    this.Invalidate();
                }
            );
            this.PutData = Polymorphic.Create(
                [Number, Number, Number],
                function(dataset_id, timestamp, value) {
                    if (dataset_id < 0 || dataset_id >= datasets.length) {
                        throw "Invalid data-set ID.";
                    }
                    rectifier[dataset_id].Put(timestamp, {
                        "Value": value
                    });
                }
            );

            //  Create the chart.
            Plotly.newPlot(element[0], figure["data"], figure["layout"], {
                "staticPlot": true
            });

            //  Invalidate immediately after created.
            this.Invalidate();

            //  Create the refresh timer.
            setInterval(function() {
                RedrawChart();
            }, interval);
        }
    );

   var FFTChart = Polymorphic.Create(
        [String, Number, Array, Number],
        function(title, length, values, interval) {
            //  Let parent class initialize.
            UI.Object.BaseObject.call(this);

//            //  Check the datasets.
//            if (datasets.length == 0) {
//                throw "No dataset.";
//            }
//            if (!Utility.CheckArrayItemType(datasets, DataSetConfiguration)) {
//                throw "Invalid item type of the datasets.";
//            }


            //  Check the interval.
            if (interval <= 0) {
                throw "Invalid interval.";
            }

            //  Check the length.
            if (length <= 0) {
                throw "Invalid time length.";
            }

//            //  Calculate the range.
//            var range_min = null, range_max = null;
//            datasets.forEach(function(dataset) {
//                var range = dataset.GetRange();
//                if (range_min === null) {
//                    range_min = range[0];
//                    range_max = range[1];
//                } else {
//                    if (range[0] < range_min) {
//                        range_min = range[0];
//                    }
//                    if (range[1] > range_max) {
//                        range_max = range[1];
//                    }
//                }
//            });
//
//            //  Check the range.
//            if (range_min >= range_max) {
//                throw "Invalid data range.";
//            }

            //  Create the figure of the chart.
            var figure = {
                "layout": {
                    "xaxis": {
                        "rangemode": "tozero",
                        "autorange": true
                    },
                    "yaxis": {
                        "rangemode": "nonnegative",
                        "autorange": true
                    },
                    "autosize": false,
                    "width": cst_InitialWidth,
                    "height": cst_InitialHeight,
                    "margin": {
                        "l": 32,
                        "r": 32,
                        "b": 32,
                        "t": 32,
                        "pad": 4
                    }
                },
                "data": [
                ]
            };
            if (title !== null && title.length > 0) {
                figure["layout"]["title"] = title;
            }

            figure["data"].push({
                    "x": [],
                    "y": [],
                    "mode": "lines",
                    "line": {
                        "color": "blue",
                        "width": 1
                    },
                    "type": "scatter",
                    "name": "FFT"
                });

            //
            //  Members.
            //
            var last_x = -1, last_y = -1;
            var x = cst_InitialX, y = cst_InitialY;
            var last_width = -1, last_height = -1;
            var width = cst_InitialWidth, height = cst_InitialHeight;
            var last_visibility = !cst_InitialVisibility;
            var visibility = cst_InitialVisibility;
            var last_layer = -1;
            var layer = cst_InitialLayer;
            var rectifier = [], data_buffer = [];
            var element = $("<div></div>").css({
                "position": "absolute",
            }).addClass("plotly-graph-div");

            //  Initialize the rectifiers and the data buffers.
//            for (var idx = 0; idx < datasets.length; ++idx) {
//                rectifier.push(new LinearTimeRectifier());
//                data_buffer.push([]);
//            }

            //
            //  Private methods.
            //
            function RecalculateData() {
                for (var idx = 0; idx < datasets.length; ++idx) {
                    //  Fetch from the rectifier.
                    var fetched = rectifier[idx].Get();
                    var rcv_ts = fetched[0], rcv_value = fetched[1];

                    //  Put the received data to the buffer.
                    for (var rcv_idx = 0; rcv_idx < rcv_ts.length; ++rcv_idx) {
                        data_buffer[idx].push([rcv_ts[rcv_idx], rcv_value[rcv_idx]]);
                    }

                    if (data_buffer[idx].length != 0) {
                        //  Get the time-stamp of the oldest sample in the buffer.
                        var oldest = data_buffer[idx][data_buffer[idx].length - 1][0];

                        //  Get the index of the first valid sample.
                        var valid = data_buffer[idx].length - 1;
                        while(valid >= 0 && data_buffer[idx][valid][0] >= oldest - length) {
                            valid--;
                        }
                        if (valid < 0) {
                            valid = 0;
                        }

                        //  Cut off useless samples in the buffer.
                        data_buffer[idx] = data_buffer[idx].splice(valid);

                        //  Build the chart data.
                        var data_x = [];
                        var data_y = [];
                        data_buffer[idx].forEach(function(data_item) {
                            data_x.push(data_item[0] - oldest);
                            data_y.push(data_item[1]["Value"]);
                        });

                        //  Write the data.
                        figure["data"][idx]["x"] = data_x;
                        figure["data"][idx]["y"] = data_y;
                    }
                }
            }

            function RedrawChart() {
//                RecalculateData();
                Plotly.redraw(element[0]);
            }

            function RelayoutChart() {
                Plotly.relayout(element[0], figure["layout"]);
            }

            //
            //  Public methods.
            //
            this.GetHTMLElement = function() {
                return element[0];
            };
            this.Invalidate = function() {
                var redraw = false;
                var relayout = false;

                //  Update the position.
                if (last_x != x) {
                    element.css({
                        "left": x
                    });
                    last_x = x;
                    redraw = true;
                }
                if (last_y != y) {
                    element.css({
                        "top": y
                    });
                    last_y = y;
                    redraw = true;
                }

                //  Update the size.
                if (last_width != width) {
                    element.css({
                        "width": width
                    });
                    figure["layout"]["width"] = width;
                    last_width = width;
                    redraw = true;
                    relayout = true;
                }
                if (last_height != height) {
                    element.css({
                        "height": height
                    });
                    figure["layout"]["height"] = height;
                    last_height = height;
                    redraw = true;
                    relayout = true;
                }

                //  Update the visibility.
                if (last_visibility != visibility) {
                    if (visibility == true) {
                        element.show();
                    } else {
                        element.hide();
                    }
                    last_visibility = visibility;
                    redraw = true;
                }

                //  Update the layer.
                if (last_layer != layer) {
                    element.css({
                        "z-index": layer
                    });
                    last_layer = layer;
                    redraw = true;
                }

                //  Relayout.
                if (relayout == true) {
                    RelayoutChart();
                }

                //  Redraw.
                if (redraw == true) {
                    RedrawChart();
                }
            };
            this.AccessPosition = Polymorphic.Create(
                [],
                function() {
                    return [x, y];
                },
                [Number, Number],
                function(new_x, new_y) {
                    last_x = x;
                    last_y = y;
                    x = new_x;
                    y = new_y;
                    this.Invalidate();
                }
            );
            this.AccessSize = Polymorphic.Create(
                [],
                function() {
                    return [width, height];
                },
                [Number, Number],
                function(new_width, new_height) {
                    last_width = width;
                    last_height = height;
                    width = new_width;
                    height = new_height;
                    this.Invalidate();
                }
            );
            this.AccessVisibility = Polymorphic.Create(
                [],
                function() {
                    return visibility;
                },
                [Boolean],
                function(new_visibility) {
                    last_visibility = visibility;
                    visibility = new_visibility;
                    this.Invalidate();
                }
            );
            this.AccessLayer = Polymorphic.Create(
                [],
                function() {
                    return layer;
                },
                [Number],
                function(new_layer) {
                    last_layer = layer;
                    layer = new_layer;
                    this.Invalidate();
                }
            );
//            this.PutData = Polymorphic.Create(
//                [Number, Number, Number],
//                function(dataset_id, timestamp, value) {
//                    if (dataset_id < 0 || dataset_id >= datasets.length) {
//                        throw "Invalid data-set ID.";
//                    }
//                    rectifier[dataset_id].Put(timestamp, {
//                        "Value": value
//                    });
//                }
//            );

            this.PutData = Polymorphic.Create(
                [Array,Array],
                function(frequencies,values) {
                       figure["data"][0]["x"] = frequencies;
                       figure["data"][0]["y"] = values;
//                       Plotly.redraw(element[0]);
                }
            );

            //  Create the chart.
            Plotly.newPlot(element[0], figure["data"], figure["layout"], {
                "staticPlot": true
            });

            //  Invalidate immediately after created.
            this.Invalidate();

            //  Create the refresh timer.
            setInterval(function() {
                RedrawChart();
            }, interval);
        }
    );
    //  Inherit "WaveformChart" from "BaseObject".
    Inherit(WaveformChart, UI.Object.BaseObject);
    Inherit(FFTChart, UI.Object.BaseObject);
    //  Export public APIs / classes.
    Namespace.Export(ns, "WaveformChart", WaveformChart);
    Namespace.Export(ns, "FFTChart", FFTChart);
});
